package cn.pm.datasource.sharding.algorithm;

import cn.hutool.core.date.DateUtil;

import cn.pm.datasource.sharding.ShardingTableNameUtils;
import cn.pm.datasource.sharding.bean.AutoCreateTablesCacheBean;
import cn.pm.datasource.sharding.converter.DateConverter;
import com.google.common.collect.Range;
import org.apache.shardingsphere.api.sharding.standard.PreciseShardingValue;
import org.apache.shardingsphere.api.sharding.standard.RangeShardingValue;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;

/**
 * Create by spf on 2019/11/29
 * <p>
 * 本分片算法处理器，仅用于根据日期字段动态分表（不分库，且会自动创建不存在的表）的业务处理
 */
public class DateBaseAutoCreateTablesShardingAlgorithm {
    private DateConverter dateConverter = new DateConverter();

    /**
     * 精确分片算法，用于=和IN。。该类需实现PreciseShardingAlgorithm接口并提供无参数的构造器
     */
    public String doShardingPrecise(Collection availableTargetNames, PreciseShardingValue shardingValue, String dateFormat) throws Exception {
        // 根据逻辑表名和参数，计算实际表名
        String realTableName = getDateTableName(shardingValue.getValue(), shardingValue.getLogicTableName(), dateFormat);

        // 从缓存中获取数据库中已有的真实表列表
        AutoCreateTablesCacheBean table = ShardingTableNameUtils.getTable(shardingValue.getLogicTableName());
        if (table == null)
            throw new Exception( "数据库动态创建表配置出错，请检查spring.shardingsphere.sharding.tables.逻辑表.table-strategy");

        if (table.getRealTableNameList() != null && !table.getRealTableNameList().isEmpty()) {
            for (String targetName : table.getRealTableNameList()) {
                if (targetName == null)
                    continue;
                if (targetName.equals(realTableName)) {
                    return targetName;
                }
            }
        }

        // 已有的实际表中，没有参数所需要的表，创建
        ShardingTableNameUtils.createTable(realTableName, table);
        return realTableName;
    }

    /**
     * 范围分片算法，用于BETWEEN，可选。。该类需实现RangeShardingAlgorithm接口并提供无参数的构造器
     */
    public Collection<String> doShardingRange(Collection availableTargetNames, RangeShardingValue shardingValue, String dateFormat) throws Exception {
        Range valueRange = shardingValue.getValueRange();
        String startTableName = null;
        String endTableName = null;
        if (valueRange.hasLowerBound()) {
            Comparable startDate = valueRange.lowerEndpoint();
            startTableName = getDateTableName(startDate, shardingValue.getLogicTableName(), dateFormat);
        }
        if (valueRange.hasUpperBound()) {
            Comparable endDate = valueRange.upperEndpoint();
            endTableName = getDateTableName(endDate, shardingValue.getLogicTableName(), dateFormat);
        }

        AutoCreateTablesCacheBean table = ShardingTableNameUtils.getTable(shardingValue.getLogicTableName());
        if (table == null)
            throw new Exception( "数据库动态创建表配置出错，请检查spring.shardingsphere.sharding.tables.逻辑表.table-strategy");

        List<String> result = new ArrayList<>();
        if (table.getRealTableNameList() != null && !table.getRealTableNameList().isEmpty()) {
            for (String targetTableName : table.getRealTableNameList()) {
                if (targetTableName == null)
                    continue;
                // 排除小于开始时间的表
                if (startTableName != null) {
                    if (targetTableName.compareTo(startTableName) < 0)
                        continue;
                }
                // 排除大于结束时间的表
                if (endTableName != null) {
                    if (targetTableName.compareTo(endTableName) > 0)
                        continue;
                }
                result.add(targetTableName);
            }
        }

        // `如果没有一张真实表符合逻辑`，将返回母表
        if (result.isEmpty())
            result.add(table.getLogicTableName());
        return result;
    }

    // 根据日期参数，转换表名
    private String getDateTableName(Comparable params, String logicTable, String dateFormat) throws Exception {
        if (params instanceof Date) {
            String dateStr = DateUtil.format((Date) params, dateFormat);
            return logicTable + dateStr;
        } else if (params instanceof String) {
            Date date = dateConverter.convert(params.toString());
            if (date == null)
                throw new Exception( "参数错误，参数:" + params.toString() + " 解析日期字段失败");
            String dateStr = DateUtil.format(date, dateFormat);
            return logicTable + dateStr;
        } else {
            throw new Exception("分片器配置错误，本分片器仅用于处理日期类型字段");
        }
    }
}
